package tech.xs.auth.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import tech.xs.auth.api.domain.bo.password.CurrUserModifyPasswordBo;
import tech.xs.auth.dao.AuthSysUserDao;
import tech.xs.auth.domain.bo.password.ImageVerifyCodeBo;
import tech.xs.auth.domain.bo.password.InitPasswordBo;
import tech.xs.auth.domain.bo.password.PasswordCertificationBo;
import tech.xs.auth.domain.bo.password.PasswordLoginBo;
import tech.xs.auth.domain.constant.AuthCacheKeyConstant;
import tech.xs.auth.domain.constant.AuthConfigKeyConstant;
import tech.xs.auth.domain.entity.AuthSysUser;
import tech.xs.auth.domain.enums.AccountType;
import tech.xs.auth.domain.enums.result.LoginResultEnum;
import tech.xs.auth.domain.exception.AuthException;
import tech.xs.auth.service.AuthPasswordService;
import tech.xs.auth.service.AuthSysUserService;
import tech.xs.auth.service.AuthTokenService;
import tech.xs.common.auth.domain.cache.AuthTokenCache;
import tech.xs.common.auth.domain.enums.AuthClientTypeEnum;
import tech.xs.common.framework.util.Assert;
import tech.xs.common.framework.util.AuthUtil;
import tech.xs.common.framework.util.SysConfigUtil;
import tech.xs.common.util.VerifyCodeUtil;

import javax.annotation.Resource;
import java.time.Duration;

@Slf4j
@Service
public class AuthPasswordServiceImpl implements AuthPasswordService {

    @Value("${auth.login.password.public-md5-salt}")
    private String publicMd5Salt;

    @Value("${auth.login.password.private-md5-salt}")
    private String privateMd5Salt;

    @Value("${auth.login.password.rsa.public-key}")
    private String rsaPublicKey;

    @Value("${auth.login.password.rsa.private-key}")
    private String rsaPrivateKey;

    @Resource
    private AuthSysUserDao loginUserDao;

    @Resource
    private AuthTokenService authTokenService;
    @Resource
    private AuthSysUserService authSysUserService;

    @Resource
    private RedissonClient redissonClient;

    @Override
    public String publicEncryptedLoginPassword(String password) {
        return DigestUtils.md5DigestAsHex((password + publicMd5Salt).getBytes());
    }

    @Override
    public String privateEncryptedLoginPassword(String password) {
        return DigestUtils.md5DigestAsHex((password + privateMd5Salt).getBytes());
    }

    @Override
    public String decryptionLoginPassword(String password) {
        RSA rsa = new RSA(rsaPrivateKey, null);
        return rsa.decryptStr(password, KeyType.PrivateKey);
    }

    @Override
    public PasswordCertificationBo getCertification() {
        PasswordCertificationBo certification = new PasswordCertificationBo();
        certification.setMd5Salt(publicMd5Salt);
        certification.setRsaPublicKey(rsaPublicKey);

        return certification;
    }

    @Override
    public ImageVerifyCodeBo getImageVerifyCode() {
        ImageVerifyCodeBo imageVerifyCodeBo = new ImageVerifyCodeBo();
        String verifyCode = VerifyCodeUtil.generateVerifyCode(4);
        imageVerifyCodeBo.setVerifyCode(VerifyCodeUtil.getImageBase64(128, 36, verifyCode));
        String verifyCodeId = IdUtil.getSnowflakeNextIdStr();
        imageVerifyCodeBo.setVerifyCodeId(verifyCodeId);
        String cacheKey = AuthCacheKeyConstant.AUTH_LOGIN_VERIFY_CODE_CACHE + verifyCodeId;
        RBucket<String> bucket = redissonClient.getBucket(cacheKey);
        bucket.set(verifyCode, Duration.ofMinutes(5));
        return imageVerifyCodeBo;
    }

    @Override
    public AuthTokenCache loginByUserName(PasswordLoginBo bo) {
        log.info("用户登陆:" + bo);
        String publicEncodePassword;
        try {
            publicEncodePassword = decryptionLoginPassword(bo.getPassword());
        } catch (Exception e) {
            throw new AuthException("用户不存在或密码错误");
        }
        String cacheKey = AuthCacheKeyConstant.AUTH_LOGIN_VERIFY_CODE_CACHE + bo.getVerifyCodeId();
        RBucket<String> bucket = redissonClient.getBucket(cacheKey);
        Assert.isTrue(bucket.isExists(), LoginResultEnum.IMAGE_VERIFY_CODE_INVALID);
        String cacheVerifyCode = bucket.get();
        bucket.delete();
        Assert.isTrue(cacheVerifyCode.equals(bo.getVerifyCode().toUpperCase()), LoginResultEnum.IMAGE_VERIFY_CODE_ERROR);

        AuthSysUser user = loginUserDao.selectOne(Wrappers.<AuthSysUser>lambdaQuery()
                .eq(AccountType.USER_NAME.equals(bo.getAccountType()), AuthSysUser::getUserName, bo.getAccount())
                .eq(AccountType.PHONE.equals(bo.getAccountType()), AuthSysUser::getPhone, bo.getAccount())
                .eq(AccountType.EMAIL.equals(bo.getAccountType()), AuthSysUser::getEmail, bo.getAccount())
        );
        authSysUserService.checkLoginUser(user);
        Assert.isTrue(user.getPassword().equals(privateEncryptedLoginPassword(publicEncodePassword)), "用户不存在或密码错误", false);
        AuthTokenCache authToken = authTokenService.createAuthToken(user, bo.getClientType());
        AuthTokenCache resData = new AuthTokenCache();
        resData.setUserName(authToken.getUserName());
        resData.setAccessToken(authToken.getAccessToken());
        if (AuthClientTypeEnum.BROWSER == bo.getClientType()) {
            return resData;
        }
        resData.setRefreshToken(authToken.getRefreshToken());
        resData.setAccessTokenInvalidTime(authToken.getAccessTokenInvalidTime());
        resData.setRefreshTokenInvalidTime(authToken.getRefreshTokenInvalidTime());
        return resData;
    }

    @Override
    public void initUserPassword(InitPasswordBo bo) {
        log.info("初始化用户密码:" + bo);
        AuthSysUser user = loginUserDao.selectOne(Wrappers.<AuthSysUser>lambdaQuery()
                .isNull(AuthSysUser::getPassword)
                .eq(AccountType.USER_NAME.equals(bo.getAccountType()), AuthSysUser::getUserName, bo.getAccount())
                .eq(AccountType.PHONE.equals(bo.getAccountType()), AuthSysUser::getPhone, bo.getAccount())
                .eq(AccountType.EMAIL.equals(bo.getAccountType()), AuthSysUser::getEmail, bo.getAccount())
        );
        Assert.isNotNull(user, 1000, "用户不存在或用户已经初始化");
        String publicEncodePassword;
        try {
            publicEncodePassword = decryptionLoginPassword(bo.getPassword());
        } catch (Exception e) {
            throw new AuthException("密码不符合规范");
        }
        String password = privateEncryptedLoginPassword(publicEncodePassword);
        loginUserDao.update(new AuthSysUser(), Wrappers.<AuthSysUser>lambdaUpdate()
                .eq(AuthSysUser::getId, user.getId())
                .set(AuthSysUser::getPassword, password));
    }

    @Override
    public void resetDefaultPassword(String userName) {
        log.info("重置用户密码:" + userName);
        AuthSysUser user = loginUserDao.selectOne(Wrappers.<AuthSysUser>lambdaQuery()
                .eq(AuthSysUser::getUserName, userName)
        );
        Assert.isNotNull(user, 1000, "用户不存在");
        String userDefaultPassword = SysConfigUtil.getStringConfigData(AuthConfigKeyConstant.USER_DEFAULT_PASSWORD);
        Assert.isNotBlank(userDefaultPassword, "系统配置不能为空,请先配置用户默认密码");
        userDefaultPassword = privateEncryptedLoginPassword(publicEncryptedLoginPassword(userDefaultPassword));
        loginUserDao.update(new AuthSysUser(), Wrappers.<AuthSysUser>lambdaUpdate()
                .eq(AuthSysUser::getId, user.getId())
                .set(AuthSysUser::getPassword, userDefaultPassword));
        log.info("重置用户密码成功 userName:" + userName);
    }

    @Override
    public void currUserModifyPassword(CurrUserModifyPasswordBo bo) {
        String oldPassword = bo.getOldPassword();
        Assert.isNotBlank(oldPassword, "原密码不能为空");
        String newPassword = bo.getNewPassword();
        Assert.isNotBlank(newPassword, "新密码不能为空");
        Long userId = AuthUtil.getCurrUserId();
        Assert.isNotNull(userId, "当前用户未登陆");
        log.info("用户修改自身密码成功 userId:" + userId);
        AuthSysUser user = authSysUserService.getById(userId);
        Assert.isNotNull(user, "当前用户不存在");
        try {
            oldPassword = decryptionLoginPassword(oldPassword);
            newPassword = privateEncryptedLoginPassword(decryptionLoginPassword(newPassword));
        } catch (Exception e) {
            throw new AuthException("原密码错误");
        }
        authSysUserService.checkLoginUser(user);
        Assert.isTrue(user.getPassword().equals(privateEncryptedLoginPassword(oldPassword)), "原密码错误", false);

        loginUserDao.update(new AuthSysUser(), Wrappers.<AuthSysUser>lambdaUpdate()
                .eq(AuthSysUser::getId, user.getId())
                .set(AuthSysUser::getPassword, newPassword));
        log.info("用户修改自身密码成功 userId:" + userId);
    }

}
